//@version=6
indicator("Dresteghamat:Adaptive Multi-TF Decision Engine", overlay=true, max_lines_count=500)

//======================== INPUTS ========================
lenATR         = input.int(14,  "ATR Length", minval=1)
lenRSI         = input.int(14,  "RSI Length", minval=2)
lenVolMA       = input.int(20,  "Volume MA Length", minval=1)
lenRunAvg      = input.int(40,  "Trend Persistence Lookback", minval=10)
structLookback = input.int(25,  "Structure Lookback (min 25)", minval=25, maxval=200)
baseHiThr      = input.float(70.0, "High Exhaustion Threshold")
baseMidThr     = input.float(40.0, "Mid Exhaustion Threshold")
microLen       = input.int(5,   "Micro Bias Window (bars)", minval=3, maxval=20)

tablePosInput  = input.string("Top Right", "Table Position", options = ["Top Right", "Top Left", "Bottom Right", "Bottom Left"])
tableSizeInput = input.string("Normal",    "Table Size",     options = ["Compact", "Normal", "Large"])

//======================== HELPERS =======================
trueRange() =>
    a = high - low
    b = math.abs(high - close[1])
    c = math.abs(low  - close[1])
    math.max(a, math.max(b, c))

clamp01(x) =>
    math.max(0.0, math.min(1.0, x))

signf(x) =>
    x > 0.0 ? 1.0 : x < 0.0 ? -1.0 : 0.0

//======================== CORE CALC (REGIME + DIRECTION + EXHAUSTION) =======================
f_rt_calc() =>
    // ---------- base metrics ----------
    atrVal  = ta.rma(trueRange(), lenATR)
    atrSafe = nz(atrVal, 1e-6)

    rsiVal  = ta.rsi(close, lenRSI)
    volMA   = ta.sma(volume, lenVolMA)

    lb        = structLookback
    closePast = nz(close[lb], close)
    atrMA     = ta.sma(atrVal, lb)
    atrBase   = atrMA > 0.0 ? atrMA : atrSafe

    rng          = ta.highest(high, lb) - ta.lowest(low, lb)
    rngAtrRatio  = atrBase > 0.0 ? rng / atrBase : 0.0
    dirDisp      = atrBase > 0.0 ? (close - closePast) / atrBase : 0.0

    // directional body sum
    cumBody      = ta.cum(close - open)
    cumBodyPast  = nz(cumBody[lb], cumBody)
    bodySum      = cumBody - cumBodyPast
    bodyDir      = signf(bodySum)

    // ---------- swing structure (Pivots) ----------
    ph = ta.pivothigh(high, 2, 2)
    pl = ta.pivotlow(low, 2, 2)

    var float lastHigh = na
    var float prevHigh = na
    var float lastLow  = na
    var float prevLow  = na

    if not na(ph)
        prevHigh := lastHigh
        lastHigh := ph

    if not na(pl)
        prevLow := lastLow
        lastLow := pl

    hasStruct     = not na(lastHigh) and not na(prevHigh) and not na(lastLow) and not na(prevLow)
    isBullStruct  = hasStruct and lastHigh > prevHigh and lastLow > prevLow
    isBearStruct  = hasStruct and lastLow < prevLow and lastHigh < prevHigh
    structScore   = isBullStruct ? 1.0 : isBearStruct ? -1.0 : 0.0

    // ---------- MICRO BIAS (last microLen bars) ----------
    mLen       = math.min(microLen, lb)
    microDisp  = atrSafe > 0.0 ? (close - nz(close[mLen], close)) / atrSafe : 0.0

    cumUp      = ta.cum(close > open ? 1.0 : 0.0)
    cumDn      = ta.cum(close < open ? 1.0 : 0.0)
    cumUpPast  = nz(cumUp[mLen], cumUp)
    cumDnPast  = nz(cumDn[mLen], cumDn)
    microUp    = cumUp - cumUpPast
    microDown  = cumDn - cumDnPast
    microBody  = microUp - microDown
    microBodySign = signf(microBody)

    microDispSign = signf(microDisp)
    microScoreDir = 0.6 * microBodySign + 0.4 * microDispSign
    microScoreDir := math.max(-1.0, math.min(1.0, microScoreDir))
    microBiasSign = signf(microScoreDir)

    // ---------- REGIME (TREND vs RANGE) ----------
    trendEvidence = 0.0
    trendEvidence += rngAtrRatio > 2.0 ? 0.5 : rngAtrRatio < 1.2 ? -0.4 : 0.0
    absDisp = math.abs(dirDisp)
    trendEvidence += absDisp > 0.8 ? 0.3 : absDisp < 0.3 ? -0.2 : 0.0
    trendEvidence += (isBullStruct or isBearStruct) ? 0.3 : -0.2

    regimeScore01 = clamp01((trendEvidence + 1.0) / 2.0)
    isTrendRegime = regimeScore01 >= 0.5
    string regime = isTrendRegime ? "TREND" : "RANGE"

    // ---------- DIRECTION (UP / DOWN / RANGE) ----------
    dirRaw = 0.0
    dirRaw += structScore * 0.5
    dirRaw += signf(dirDisp) * 0.25
    dirRaw += bodyDir * 0.15
    dirRaw += microBiasSign * 0.10
    dirRaw := math.max(-1.0, math.min(1.0, dirRaw))

    dirSign = dirRaw > 0.25 ? 1 : dirRaw < -0.25 ? -1 : 0

    string type = "RANGE"
    if isTrendRegime
        if dirSign == 1
            type := "UP"
        else if dirSign == -1
            type := "DOWN"
        else
            type := "RANGE"
    else
        type := "RANGE"

    // ---------- EXHAUSTION MODULES ----------
    // MID: RSI + impulse divergence
    impulse     = (close - close[1]) / atrSafe
    impulsePrev = nz(impulse[1])
    impDropRaw  = impulse < impulsePrev ? (impulsePrev - impulse) / (math.abs(impulsePrev) + 1e-6) : 0.0
    impulseDrop = clamp01(impDropRaw)

    rsiRaw =
         dirSign == 1 and rsiVal > 60.0 ? (rsiVal - 60.0) / 40.0 :
         dirSign == -1 and rsiVal < 40.0 ? (40.0 - rsiVal) / 40.0 :
         0.0
    rsiEx = clamp01(rsiRaw)

    priceUp   = close > close[1] and close[1] > close[2]
    priceDown = close < close[1] and close[1] < close[2]
    rsiDown   = rsiVal < rsiVal[1]
    rsiUp     = rsiVal > rsiVal[1]

    div = (dirSign == 1 and priceUp and rsiDown) or (dirSign == -1 and priceDown and rsiUp) ? 1.0 : 0.0
    mid = clamp01(0.5 * rsiEx + 0.4 * impulseDrop + 0.6 * div)

    // VRSD: volatility regime shift
    atrMA2   = ta.sma(atrVal, lenATR * 2)
    atrNorm  = atrMA2 > 0.0 ? atrVal / atrMA2 : 1.0
    wasExp   = nz(atrNorm[5]) > 1.2
    nowComp  = atrNorm < 0.9
    baseVR   = wasExp and nowComp ? 0.6 : 0.0
    comp     = clamp01(1.0 - atrNorm)
    vrsd     = clamp01(baseVR + 0.4 * comp)

    // VEFF: volume effect near extremes
    isNearHigh = close >= ta.highest(close, 10)
    isNearLow  = close <= ta.lowest(close, 10)
    volDryRaw = volMA > 0.0 ? (volMA - volume) / volMA : 0.0
    volDry    = clamp01(volDryRaw)
    volFlag = (dirSign == 1 and isNearHigh and volDry > 0.0) or (dirSign == -1 and isNearLow and volDry > 0.0) ? 0.6 : 0.0
    veff = clamp01(volFlag + 0.4 * volDry)

    // MSE: structural exhaustion
    lowerHigh = high < high[1] and high[1] > high[2]
    higherLow = low  > low[1]  and low[1]  < low[2]
    weakUp   = dirSign == 1 and lowerHigh
    weakDown = dirSign == -1 and higherLow
    bodyNorm   = math.abs(close - open) / atrSafe
    smallBody  = bodyNorm < 0.2
    smallBody3 = smallBody and smallBody[1] and smallBody[2]
    mseRaw = (weakUp or weakDown ? 0.6 : 0.0) + (smallBody3 ? 0.4 : 0.0)
    mse    = clamp01(mseRaw)

    // STEM: trend persistence based on dirSign
    trendActive  = math.abs(dirSign)
    trendPersist = ta.sma(trendActive, lenRunAvg)
    stem         = clamp01((trendPersist - 0.5) * 2.0)

    // Micro Opposite Pressure
    microOpp = isTrendRegime and dirSign != 0 and microBiasSign == -dirSign
    microOppStrength = microOpp ? clamp01(math.abs(microDisp)) : 0.0
    extraMicro = microOppStrength * 0.3

    exhaust01    = clamp01(0.28 * mid + 0.18 * vrsd + 0.18 * veff + 0.16 * mse + 0.15 * stem + extraMicro)
    exhaustScore = exhaust01 * 100.0

    hiThr  = baseHiThr
    midThr = baseMidThr
    adj = atrNorm > 1.3 ? 1.1 : atrNorm < 0.8 ? 0.9 : 1.0
    hiThr  := hiThr * adj
    midThr := midThr * adj
    hiThr  := hiThr * (1.0 - 0.15 * regimeScore01)
    midThr := midThr * (1.0 - 0.15 * regimeScore01)

    string status = "NEUTRAL"
    float trendScore01 = 0.0

    if not isTrendRegime
        status       := "NEUTRAL"
        trendScore01 := 0.0
    else
        if exhaustScore >= hiThr
            status := "EXHAUSTED"
        else if exhaustScore >= midThr
            status := "WEAKENING"
        else
            status := "STRONG"
        trendScore01 := clamp01(regimeScore01 * trendPersist * (1.0 - exhaust01))

    float finalScore = trendScore01 * 100.0

    if not isTrendRegime
        finalScore := 30.0 + 20.0 * regimeScore01
        status     := "NEUTRAL"
        type       := "RANGE"

    [regime, type, status, finalScore]

//======================== FROZEN WRAPPER =======================
f_rt_frozen() =>
    var string regimeFrozen  = ""
    var string typeFrozen    = ""
    var string statusFrozen  = ""
    var float  scoreFrozen   = 0.0

    [regNow, typeNow, statusNow, scoreNow] = f_rt_calc()

    if barstate.isconfirmed
        regimeFrozen  := regNow
        typeFrozen    := typeNow
        statusFrozen  := statusNow
        scoreFrozen   := scoreNow

    [regimeFrozen, typeFrozen, statusFrozen, scoreFrozen]

//======================== MTF CALLS ============================
[rgC, ttC, tsC, scC] = f_rt_frozen()

[rg5,   tt5,   ts5,   sc5]   = request.security(syminfo.tickerid, "5",   f_rt_frozen(), barmerge.gaps_off, barmerge.lookahead_off)
[rg15,  tt15,  ts15,  sc15]  = request.security(syminfo.tickerid, "15",  f_rt_frozen(), barmerge.gaps_off, barmerge.lookahead_off)
[rg60,  tt60,  ts60,  sc60]  = request.security(syminfo.tickerid, "60",  f_rt_frozen(), barmerge.gaps_off, barmerge.lookahead_off)
[rg240, tt240, ts240, sc240] = request.security(syminfo.tickerid, "240", f_rt_frozen(), barmerge.gaps_off, barmerge.lookahead_off)
[rgD,   ttD,   tsD,   scD]   = request.security(syminfo.tickerid, "D",   f_rt_frozen(), barmerge.gaps_off, barmerge.lookahead_off)

//======================== DYNAMIC DECISION ENGINE (ADAPTIVE HTF) ============================
dirFromType(t) =>
    t == "UP" ? 1.0 : t == "DOWN" ? -1.0 : 0.0

dirC   = dirFromType(ttC)
dir5   = dirFromType(tt5)
dir15  = dirFromType(tt15)
dir60  = dirFromType(tt60)
dir240 = dirFromType(tt240)
dirD   = dirFromType(ttD)

// 1. Detect Current TF in Minutes
float tfMin = timeframe.multiplier
if timeframe.isseconds
    tfMin := tfMin / 60.0
if timeframe.isdaily
    tfMin := 1440.0
if timeframe.isweekly
    tfMin := 10080.0
if timeframe.ismonthly
    tfMin := 43200.0

// 2. Define Dynamic HTF Variables (HTF1=Intermediate, HTF2=Anchor)
float dH1 = 0.0, float sH1 = 0.0, bool eH1 = false
float dH2 = 0.0, float sH2 = 0.0, bool eH2 = false

// 3. Map HTFs based on context
if tfMin <= 5
    // Scalping (<5m): Look at 15m & 1H
    dH1 := dir15,  sH1 := sc15,  eH1 := ts15 == "EXHAUSTED"
    dH2 := dir60,  sH2 := sc60,  eH2 := ts60 == "EXHAUSTED"
else if tfMin <= 15
    // Day Trading (<15m): Look at 1H & 4H
    dH1 := dir60,  sH1 := sc60,  eH1 := ts60 == "EXHAUSTED"
    dH2 := dir240, sH2 := sc240, eH2 := ts240 == "EXHAUSTED"
else if tfMin <= 60
    // Swing Intraday (<1H): Look at 4H & Daily
    dH1 := dir240, sH1 := sc240, eH1 := ts240 == "EXHAUSTED"
    dH2 := dirD,   sH2 := scD,   eH2 := tsD   == "EXHAUSTED"
else
    // Position (>1H): Look at Daily (Doubled weight)
    dH1 := dirD,   sH1 := scD,   eH1 := tsD == "EXHAUSTED"
    dH2 := dirD,   sH2 := scD,   eH2 := tsD == "EXHAUSTED"

// 4. Calculate Weighted Score (Dynamic)
// Weight: Current(40%) + HTF1(30%) + HTF2(30%)
float wC_dyn  = 0.4
float wH1_dyn = 0.3
float wH2_dyn = 0.3

// Note: scC/100.0 converts score to 0-1 range
float numDyn = dirC * (scC/100.0) * wC_dyn + dH1 * (sH1/100.0) * wH1_dyn + dH2 * (sH2/100.0) * wH2_dyn
float denDyn = wC_dyn + wH1_dyn + wH2_dyn

float finalDir  = numDyn // Range -1 to 1
float finalConf = math.abs(finalDir) * 100.0
string biasStr  = finalDir > 0.2 ? "BULLISH" : finalDir < -0.2 ? "BEARISH" : "FLAT"

// 5. Determine Final Action
string action = "WAIT"
color modeCol = color.gray

// Alignment check: Is Current TF aligned with Anchor HTF (or Anchor is neutral)?
bool aligned = signf(dirC) == signf(dH2) or dirC == 0 or dH2 == 0

if finalDir > 0.35 and finalConf > 35
    if eH2
        action := "HTF EXHAUST"
        modeCol := color.orange
    else if not aligned and dirC < 0
        // Structure is Bullish, but we are going down -> Pullback
        action := "PULLBACK"
        modeCol := color.yellow
    else
        action := "BUY ONLY"
        modeCol := color.lime
else if finalDir < -0.35 and finalConf > 35
    if eH2
        action := "HTF EXHAUST"
        modeCol := color.orange
    else if not aligned and dirC > 0
        // Structure is Bearish, but we are going up -> Pullback
        action := "PULLBACK"
        modeCol := color.yellow
    else
        action := "SELL ONLY"
        modeCol := color.red
else
    action := "WAIT"
    modeCol := color.gray

//======================== TABLE CONFIG ============================
var tablePos = position.top_right
if barstate.isfirst
    if tablePosInput == "Top Right"
        tablePos := position.top_right
    else if tablePosInput == "Top Left"
        tablePos := position.top_left
    else if tablePosInput == "Bottom Right"
        tablePos := position.bottom_right
    else
        tablePos := position.bottom_left

var fontSize = size.small
if barstate.isfirst
    if tableSizeInput == "Compact"
        fontSize := size.tiny
    else if tableSizeInput == "Large"
        fontSize := size.large
    else
        fontSize := size.small

bgHead = color.new(color.rgb(30, 30, 40), 10)
bg1    = color.new(color.rgb(25, 25, 35), 15)
bg2    = color.new(color.rgb(20, 20, 30), 20)
txt    = color.white

//======================== TABLE RENDER ============================
var table t = na
if barstate.isfirst
    // 5 columns, 8 rows
    t := table.new(tablePos, 5, 8, border_width=1, border_color=color.new(color.gray, 60))

if barstate.islast and not na(t)
    // Header
    table.cell(t, 0, 0, "TF",      text_color=txt, bgcolor=bgHead, text_size=fontSize)
    table.cell(t, 1, 0, "Regime",  text_color=txt, bgcolor=bgHead, text_size=fontSize)
    table.cell(t, 2, 0, "Type",    text_color=txt, bgcolor=bgHead, text_size=fontSize)
    table.cell(t, 3, 0, "Status",  text_color=txt, bgcolor=bgHead, text_size=fontSize)
    table.cell(t, 4, 0, "Score",   text_color=txt, bgcolor=bgHead, text_size=fontSize)

    // Row 1 - CURRENT
    table.cell(t, 0, 1, "CURRENT",                 text_color=txt, bgcolor=bg1, text_size=fontSize)
    table.cell(t, 1, 1, rgC,                       text_color=txt, bgcolor=bg1, text_size=fontSize)
    table.cell(t, 2, 1, ttC,                       text_color=txt, bgcolor=bg1, text_size=fontSize)
    table.cell(t, 3, 1, tsC,                       text_color=txt, bgcolor=bg1, text_size=fontSize)
    table.cell(t, 4, 1, str.tostring(scC, "#.0"),  text_color=txt, bgcolor=bg1, text_size=fontSize)

    // Row 2 - 5m
    table.cell(t, 0, 2, "5m",                      text_color=txt, bgcolor=bg2, text_size=fontSize)
    table.cell(t, 1, 2, rg5,                       text_color=txt, bgcolor=bg2, text_size=fontSize)
    table.cell(t, 2, 2, tt5,                       text_color=txt, bgcolor=bg2, text_size=fontSize)
    table.cell(t, 3, 2, ts5,                       text_color=txt, bgcolor=bg2, text_size=fontSize)
    table.cell(t, 4, 2, str.tostring(sc5, "#.0"),  text_color=txt, bgcolor=bg2, text_size=fontSize)

    // Row 3 - 15m
    table.cell(t, 0, 3, "15m",                     text_color=txt, bgcolor=bg1, text_size=fontSize)
    table.cell(t, 1, 3, rg15,                      text_color=txt, bgcolor=bg1, text_size=fontSize)
    table.cell(t, 2, 3, tt15,                      text_color=txt, bgcolor=bg1, text_size=fontSize)
    table.cell(t, 3, 3, ts15,                      text_color=txt, bgcolor=bg1, text_size=fontSize)
    table.cell(t, 4, 3, str.tostring(sc15, "#.0"), text_color=txt, bgcolor=bg1, text_size=fontSize)

    // Row 4 - 1H
    table.cell(t, 0, 4, "1H",                      text_color=txt, bgcolor=bg2, text_size=fontSize)
    table.cell(t, 1, 4, rg60,                      text_color=txt, bgcolor=bg2, text_size=fontSize)
    table.cell(t, 2, 4, tt60,                      text_color=txt, bgcolor=bg2, text_size=fontSize)
    table.cell(t, 3, 4, ts60,                      text_color=txt, bgcolor=bg2, text_size=fontSize)
    table.cell(t, 4, 4, str.tostring(sc60, "#.0"), text_color=txt, bgcolor=bg2, text_size=fontSize)

    // Row 5 - 4H
    table.cell(t, 0, 5, "4H",                      text_color=txt, bgcolor=bg1, text_size=fontSize)
    table.cell(t, 1, 5, rg240,                     text_color=txt, bgcolor=bg1, text_size=fontSize)
    table.cell(t, 2, 5, tt240,                     text_color=txt, bgcolor=bg1, text_size=fontSize)
    table.cell(t, 3, 5, ts240,                     text_color=txt, bgcolor=bg1, text_size=fontSize)
    table.cell(t, 4, 5, str.tostring(sc240, "#.0"),text_color=txt, bgcolor=bg1, text_size=fontSize)

    // Row 6 - 1D
    table.cell(t, 0, 6, "1D",                      text_color=txt, bgcolor=bg2, text_size=fontSize)
    table.cell(t, 1, 6, rgD,                       text_color=txt, bgcolor=bg2, text_size=fontSize)
    table.cell(t, 2, 6, ttD,                       text_color=txt, bgcolor=bg2, text_size=fontSize)
    table.cell(t, 3, 6, tsD,                       text_color=txt, bgcolor=bg2, text_size=fontSize)
    table.cell(t, 4, 6, str.tostring(scD, "#.0"),  text_color=txt, bgcolor=bg2, text_size=fontSize)

    // Row 7 - MODE (Decision)
    table.cell(t, 0, 7, "MODE",                    text_color=txt, bgcolor=modeCol, text_size=fontSize)
    table.cell(t, 1, 7, action,                    text_color=txt, bgcolor=modeCol, text_size=fontSize)
    table.cell(t, 2, 7, biasStr,                   text_color=txt, bgcolor=modeCol, text_size=fontSize)
    table.cell(t, 3, 7, "",                        text_color=txt, bgcolor=modeCol, text_size=fontSize)
    table.cell(t, 4, 7, str.tostring(finalConf, "#.0"), text_color=txt, bgcolor=modeCol, text_size=fontSize)